1 module eastasianwidth;
2 
3 public import eastasianwidth.eastasianwidth;
4 
5 import std.traits;
6 
7 ///
8 enum AmbiguousCharWidth : bool {
9     narrow,
10     wide
11 }
12 
13 /// returns monospace display width of a character
14 size_t displayWidth(T)(T ch, AmbiguousCharWidth acw = AmbiguousCharWidth.narrow)
15 if (isSomeChar!T) {
16     auto prop = eastAsianWidth(ch);
17     if (prop == EastAsianWidthProperty.F || prop == EastAsianWidthProperty.W) {
18         return 2;
19     } else if (prop == EastAsianWidthProperty.A) {
20         return (acw == AmbiguousCharWidth.narrow) ? 1 : 2;
21     } else {
22         return 1;
23     }
24 }
25 
26 /// returns monospace display width of a string
27 size_t displayWidth(T)(T str, AmbiguousCharWidth acw = AmbiguousCharWidth.narrow)
28 if (isSomeString!T) {
29     size_t width;
30     foreach (dchar ch; str) {
31         width += displayWidth(ch, acw);
32     }
33     return width;
34 }
35 
36 ///
37 @safe pure @nogc unittest {
38     assert(displayWidth("あいうえお") == 10);
39 
40     // '☆' is Ambiguous characters
41     assert(displayWidth('☆') == 1);
42     assert(displayWidth('☆', AmbiguousCharWidth.wide) == 2);
43     assert(displayWidth("☆D言語くん☆") == 11);
44     assert(displayWidth("☆D言語くん☆", AmbiguousCharWidth.wide) == 13);
45 
46     assert(eastAsianWidth('A') == EastAsianWidthProperty.F); // Fullwidth
47     assert(eastAsianWidth('ア') == EastAsianWidthProperty.H);  // Halfwidth
48     assert(eastAsianWidth('ア') == EastAsianWidthProperty.W); // Wide
49     assert(eastAsianWidth('A') == EastAsianWidthProperty.Na); // Narrow
50     assert(eastAsianWidth('☆') == EastAsianWidthProperty.A); // Ambiguous
51     assert(eastAsianWidth('À') == EastAsianWidthProperty.N);  // Neutral
52 }
53 
54 @safe pure @nogc nothrow unittest {
55     assert(eastAsianWidth('A') == EastAsianWidthProperty.F);
56     assert(eastAsianWidth('!') == EastAsianWidthProperty.F);
57     assert(eastAsianWidth('₩') == EastAsianWidthProperty.F);
58 
59     assert(eastAsianWidth('ア') == EastAsianWidthProperty.H);
60     assert(eastAsianWidth('←') == EastAsianWidthProperty.H);
61     assert(eastAsianWidth('₩') == EastAsianWidthProperty.H);
62 
63     assert(eastAsianWidth('ア') == EastAsianWidthProperty.W);
64     assert(eastAsianWidth('𛀀') == EastAsianWidthProperty.W);
65     assert(eastAsianWidth('뀀') == EastAsianWidthProperty.W);
66 
67     assert(eastAsianWidth('A') == EastAsianWidthProperty.Na);
68     assert(eastAsianWidth('¢') == EastAsianWidthProperty.Na);
69     assert(eastAsianWidth('⟦') == EastAsianWidthProperty.Na);
70 
71     assert(eastAsianWidth('¿') == EastAsianWidthProperty.A);
72     assert(eastAsianWidth('Σ') == EastAsianWidthProperty.A);
73     assert(eastAsianWidth('℃') == EastAsianWidthProperty.A);
74 
75     assert(eastAsianWidth('©') == EastAsianWidthProperty.N);
76     assert(eastAsianWidth('À') == EastAsianWidthProperty.N);
77     assert(eastAsianWidth('ي') == EastAsianWidthProperty.N);
78 }
79 
80 @safe pure @nogc nothrow unittest {
81     assert(displayWidth('A') == 2);
82     assert(displayWidth('!') == 2);
83     assert(displayWidth('₩') == 2);
84 
85     assert(displayWidth('ア') == 1);
86     assert(displayWidth('←') == 1);
87     assert(displayWidth('₩') == 1);
88 
89     assert(displayWidth('ア') == 2);
90     assert(displayWidth('𛀀') == 2);
91     assert(displayWidth('뀀') == 2);
92 
93     assert(displayWidth('A') == 1);
94     assert(displayWidth('¢') == 1);
95     assert(displayWidth('⟦') == 1);
96 
97     assert(displayWidth('¿') == 1);
98     assert(displayWidth('Σ') == 1);
99     assert(displayWidth('℃') == 1);
100 
101     assert(displayWidth('©') == 1);
102     assert(displayWidth('À') == 1);
103     assert(displayWidth('ي') == 1);
104 }
105 
106 @safe pure @nogc nothrow unittest {
107     assert(displayWidth('A', AmbiguousCharWidth.wide) == 2);
108     assert(displayWidth('!', AmbiguousCharWidth.wide) == 2);
109     assert(displayWidth('₩', AmbiguousCharWidth.wide) == 2);
110 
111     assert(displayWidth('ア', AmbiguousCharWidth.wide) == 1);
112     assert(displayWidth('←', AmbiguousCharWidth.wide) == 1);
113     assert(displayWidth('₩', AmbiguousCharWidth.wide) == 1);
114 
115     assert(displayWidth('ア', AmbiguousCharWidth.wide) == 2);
116     assert(displayWidth('𛀀', AmbiguousCharWidth.wide) == 2);
117     assert(displayWidth('뀀', AmbiguousCharWidth.wide) == 2);
118 
119     assert(displayWidth('A', AmbiguousCharWidth.wide) == 1);
120     assert(displayWidth('¢', AmbiguousCharWidth.wide) == 1);
121     assert(displayWidth('⟦', AmbiguousCharWidth.wide) == 1);
122 
123     assert(displayWidth('¿', AmbiguousCharWidth.wide) == 2);
124     assert(displayWidth('Σ', AmbiguousCharWidth.wide) == 2);
125     assert(displayWidth('℃', AmbiguousCharWidth.wide) == 2);
126 
127     assert(displayWidth('©', AmbiguousCharWidth.wide) == 1);
128     assert(displayWidth('À', AmbiguousCharWidth.wide) == 1);
129     assert(displayWidth('ي', AmbiguousCharWidth.wide) == 1);
130 }
131 
132 @safe pure @nogc unittest {
133     assert(displayWidth("D-Man") == 5);
134     assert(displayWidth("D-Man", AmbiguousCharWidth.wide) == 5);
135     assert(displayWidth("「D言語くん」") == 12);
136     assert(displayWidth("「D言語くん」", AmbiguousCharWidth.wide) == 12);
137     assert(displayWidth("Ελληνικά") == 8);
138     assert(displayWidth("Ελληνικά", AmbiguousCharWidth.wide) == 15);
139     assert(displayWidth("اللغة العربية") == 13);
140     assert(displayWidth("اللغة العربية", AmbiguousCharWidth.wide) == 13);
141     assert(displayWidth("ლ(́⊙ɛ⊙`)ლ") == 9);
142     assert(displayWidth("ლ(́⊙ɛ⊙`)ლ", AmbiguousCharWidth.wide) == 12);
143 }